Crate minitrace

source ·
Expand description

minitrace is a high-performance, ergonomic, library-level timeline tracing library for Rust.

Unlike most tracing libraries which are primarily designed for instrumenting executables, minitrace also accommodates the need for library instrumentation. It stands out due to its extreme lightweight and fast performance compared to other tracing libraries. Moreover, it has zero overhead when not enabled in the executable, making it a worry-free choice for libraries concerned about unnecessary performance loss.

§Getting Started

§In Libraries

Libraries should include minitrace as a dependency without enabling any extra features.

[dependencies]
minitrace = "0.6"

Add a trace attribute to the function you want to trace. In this example, a SpanRecord will be collected every time the function is called, if a tracing context is set up by the caller.

#[minitrace::trace]
pub fn send_request(req: HttpRequest) -> Result<(), Error> {
    // ...
}

Libraries are able to set up an individual tracing context, regardless of whether the caller has set up a tracing context or not. This can be achieved by using Span::root() to start a new trace and Span::set_local_parent() to set up a local context for the current thread.

The full_name!() macro can detect the function’s full name, which is used as the name of the root span.

use minitrace::prelude::*;

pub fn send_request(req: HttpRequest) -> Result<(), Error> {
    let root = Span::root(full_name!(), SpanContext::random());
    let _guard = root.set_local_parent();

    // ...
}

§In Applications

Applications should include minitrace as a dependency with the enable feature set. To disable minitrace statically, simply remove the enable feature.

[dependencies]
minitrace = { version = "0.6", features = ["enable"] }

Applications should initialize a Reporter implementation early in the program’s runtime. Span records generated before the reporter is initialized will be ignored. Before terminating, flush() should be called to ensure all collected span records are reported.

When the root span is dropped, all of its children spans and itself will be reported at once. Since that, it’s recommended to create root spans for short tasks, such as handling a request, just like the example below. Otherwise, an endingless trace will never be reported. To override this behavior, set the report_before_root_finish option to true in the Config.

use minitrace::collector::Config;
use minitrace::collector::ConsoleReporter;
use minitrace::prelude::*;

fn main() {
    minitrace::set_reporter(ConsoleReporter, Config::default());

    loop {
        let root = Span::root("worker-loop", SpanContext::random());
        let _guard = root.set_local_parent();

        handle_request();
    }

    minitrace::flush();
}

§Key Concepts

minitrace operates through three types: Span, LocalSpan, and Event, each representing a different type of tracing record. The macro trace is available to manage these types automatically. For Future instrumentation, necessary utilities are provided by FutureExt.

§Span

A Span represents an individual unit of work. It contains:

  • A name
  • A start timestamp and duration
  • A set of key-value properties
  • A reference to a parent Span

A new Span can be started through Span::root(), requiring the trace id and the parent span id from a remote source. If there’s no remote parent span, the parent span id is typically set to its default value of zero.

Once we have the root Span, we can create a child Span using Span::enter_with_parent(), thereby establishing the reference relationship between the spans.

Span is thread-safe and can be sent across threads.

use minitrace::collector::Config;
use minitrace::collector::ConsoleReporter;
use minitrace::prelude::*;

minitrace::set_reporter(ConsoleReporter, Config::default());

{
    let root_span = Span::root("root", SpanContext::random());

    {
        let child_span = Span::enter_with_parent("a child span", &root_span);

        // ...

        // child_span ends here.
    }

    // root_span ends here.
}

minitrace::flush();

Sometimes, passing a Span through a function to create a child Span can be inconvenient. We can employ a thread-local approach to avoid an explicit argument passing in the function. In minitrace, Span::set_local_parent() and Span::enter_with_local_parent() serve this purpose.

Span::set_local_parent() method sets a local context of the Span for the current thread. Span::enter_with_local_parent() accesses the parent Span from the local context and creates a child Span with it.

use minitrace::prelude::*;

{
    let root_span = Span::root("root", SpanContext::random());
    let _guard = root_span.set_local_parent();

    foo();

    // root_span ends here.
}

fn foo() {
    // The parent of this span is `root`.
    let _child_span = Span::enter_with_local_parent("a child span");

    // ...

    // _child_span ends here.
}

§Local Span

In a clear single-thread execution flow, where we can ensure that the Span does not cross threads, meaning:

  • The Span is not sent to or shared by other threads
  • In asynchronous code, the lifetime of the Span doesn’t cross an .await point

we can use LocalSpan as a substitute for Span to effectively reduce overhead and greatly enhance performance.

However, there is a precondition: The creation of LocalSpan must take place within a local context of a Span, which is established by invoking the Span::set_local_parent() method.

If the code spans multiple function calls, this isn’t always straightforward to confirm if the precondition is met. As such, it’s recommended to invoke Span::set_local_parent() immediately after the creation of Span.

After a local context of a Span is set using Span::set_local_parent(), use LocalSpan::enter_with_local_parent() to start a LocalSpan, which then becomes the new local parent.

If no local context is set, the LocalSpan::enter_with_local_parent() will do nothing.

use minitrace::collector::Config;
use minitrace::collector::ConsoleReporter;
use minitrace::prelude::*;

minitrace::set_reporter(ConsoleReporter, Config::default());

{
    let root = Span::root("root", SpanContext::random());
    let _guard = root.set_local_parent();

    {
        // The parent of this span is `root`.
        let _span1 = LocalSpan::enter_with_local_parent("a child span");

        foo();
    }
}

fn foo() {
    // The parent of this span is `span1`.
    let _span2 = LocalSpan::enter_with_local_parent("a child span of child span");
}

minitrace::flush();

§Event

Event represents a single point in time where something occurred during the execution of a program.

An Event can be seen as a log record attached to a span.

use minitrace::collector::Config;
use minitrace::collector::ConsoleReporter;
use minitrace::prelude::*;

minitrace::set_reporter(ConsoleReporter, Config::default());

{
    let root = Span::root("root", SpanContext::random());
    let _guard = root.set_local_parent();

    Event::add_to_parent("event in root", &root, || []);
    {
        let _span1 = LocalSpan::enter_with_local_parent("a child span");

        Event::add_to_local_parent("event in span1", || [("key".into(), "value".into())]);
    }
}

minitrace::flush();

§Macro

The attribute-macro trace helps to reduce boilerplate.

Note: For successful tracing a function using the trace macro, the function call should occur within a local context of a Span.

For more detailed usage instructions, please refer to trace.

use futures::executor::block_on;
use minitrace::collector::Config;
use minitrace::collector::ConsoleReporter;
use minitrace::prelude::*;

#[trace]
fn do_something(i: u64) {
    std::thread::sleep(std::time::Duration::from_millis(i));
}

#[trace]
async fn do_something_async(i: u64) {
    futures_timer::Delay::new(std::time::Duration::from_millis(i)).await;
}

minitrace::set_reporter(ConsoleReporter, Config::default());

{
    let root = Span::root("root", SpanContext::random());
    let _guard = root.set_local_parent();

    do_something(100);

    block_on(
        async {
            do_something_async(100).await;
        }
        .in_span(Span::enter_with_local_parent("aync_job")),
    );
}

minitrace::flush();

§Reporter

Reporter is responsible for reporting the span records to a remote agent, such as Jaeger.

Executables should initialize a reporter implementation early in the program’s runtime. Span records generated before the reporter is initialized will be ignored.

For an easy start, minitrace offers a ConsoleReporter that prints span records to stderr. For more advanced use, crates like minitrace-jaeger, minitrace-datadog, and minitrace-opentelemetry are available.

By default, the reporter is triggered every 500 milliseconds. The reporter can also be triggered manually by calling flush(). See Config for customizing the reporting behavior.

use std::time::Duration;

use minitrace::collector::Config;
use minitrace::collector::ConsoleReporter;

minitrace::set_reporter(
    ConsoleReporter,
    Config::default().batch_report_interval(Duration::from_secs(1)),
);

minitrace::flush();

§Performance

minitrace is designed to be fast and lightweight, considering four scenarios:

  • No Tracing: If the feature enable is not set in the application, minitrace will be completely optimized away from the final executable binary, achieving zero overhead.

  • Sample Tracing: If enable is set in the application, but only a small portion of the traces are enabled via Span::root(), while the other portions are started with placeholders using Span::noop(). The overhead in this case is very small - merely an integer load, comparison, and jump.

  • Full Tracing with Tail Sampling: If enable is set in the application, and all traces are enabled, however, only a select few interesting tracing records (e.g., P99) are reported, while normal traces are dismissed by using Span::cancel() to avoid being reported, the overhead of collecting traces is still very small. This could be useful when you are interested in examining program’s tail latency.

  • Full Tracing: If enable is set in the application, and all traces are reported, minitrace performs 10x to 100x faster than other tracing libraries in this case.

Modules§

  • Collector and the collected spans.
  • This module provides tools to trace a Future.
  • Non thread-safe span with low overhead.
  • A “prelude” for crates using minitrace.

Macros§

  • Get the source file location where the macro is invoked. Returns a &'static str.
  • Get the full path of the function where the macro is invoked. Returns a &'static str.
  • Get the name of the function where the macro is invoked. Returns a &'static str.

Structs§

  • An event that represents a single point in time during the execution of a span.
  • A thread-safe span.

Functions§

  • Flushes all pending span records to the reporter immediately.
  • Sets the reporter and its configuration for the current application.

Attribute Macros§

  • An attribute macro designed to eliminate boilerplate code.